<?php
namespace app\framework\kline;

use app\framework\lib\Redis;
use app\framework\lib\Log;
use app\service\CurrencyKline;
use Workerman\Worker;
use Workerman\Crontab\Crontab;
use Exception;


class CryptoCurrencyKline
{
    // 单例实例（避免静态方法滥用）
    private static ?self $instance = null;

    // 时间周期（统一小写，避免重复转换）
    private array $intervals = ['1min', '5min', '15min', '30min', '60min', '1day', '1mon', '1week', '1year'];

    // 单次获取数据量
    private const DATA_NUM = 900;

    // 火币API地址模板（常量大写，明确含义）
    private const HUOBI_API_URL = "https://api.huobi.pro/market/history/kline?symbol=[coin]usdt&size=[size]&period=[period]";

    // Redis键前缀（常量规范化）
    private const REDIS_KEY_PREFIX = "market.%susdt.kline.";
    private const REDIS_YESTERDAY_SUFFIX = "yesterday";

    // 定时任务处理的周期（每日/每月）
    private array $cronPeriods = ['1day', '1mon'];

    // 币种列表（实例属性，避免静态共享导致的多进程问题）
    private array $coins = [];




    /**
     * 单例模式入口（禁止外部实例化）
     */
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 私有构造方法（确保单例）
     */
    private function __construct() {}

    /**
     * 初始化方法（替代原静态init，绑定Worker上下文）
     */
    public function init(?Worker $worker): void
    {
        // 初始化币种列表（转为小写，去重）
        $this->coins = array_unique(array_map('strtolower', self::getCoins()));
        if (empty($this->coins)) {
            Log::warning("未获取到有效币种列表，K线初始化失败");
            return;
        }


        $this->setHistoryList($this->coins, $this->intervals);
        // 启动定时任务（每天20:50执行）
        $this->startCrontab();
    }



    /**
     * 启动定时任务（每日更新指定周期的K线数据）
     */
    private function startCrontab(): void
    {
        // 每天20:50执行定时任务（处理1day/1mon周期）
        new Crontab('50 20 * * *', function () {
            Log::info("开始执行每日K线定时任务");
            $this->setHistoryList($this->coins, $this->cronPeriods);
            Log::info("每日K线定时任务执行结束");
        });
    }

    /**
     * 获取币种列表（封装为私有方法，明确依赖）
     */
    private static function getCoins(): array
    {
        try {
            $coins = CurrencyKline::getMarketList();
            return is_array($coins) ? $coins : [];
        } catch (Exception $e) {
            Log::error("获取币种列表失败：{$e->getMessage()}");
            return [];
        }
    }

    /**
     * 拉取并存储历史K线数据（核心逻辑优化）
     * @param array $coins 币种列表
     * @param array $intervals 时间周期列表
     */
    public function setHistoryList(array $coins, array $intervals): void
    {
        foreach ($coins as $coin) {
            // 跳过无效币种
            if (!is_string($coin) || trim($coin) === '') {
                Log::warning("无效币种：" . json_encode($coin));
                continue;
            }

            foreach ($intervals as $interval) {
                // 跳过未定义的周期（安全校验）
                if (!in_array($interval, $this->intervals, true)) {
                    Log::warning("未支持的时间周期：{$interval}，币种：{$coin}");
                    continue;
                }
                $this->processSingleKline($coin, $interval);
                usleep(500000); // 500ms间隔，避免API限流（替代sleep(2)，更灵活）
                Log::info("对应的币种和时间",["coin"=>$coin,"interval"=>$interval]);
            }
        }
    }

    /**
     * 处理单个币种+周期的K线数据（拆分逻辑，降低复杂度）
     */
    private function processSingleKline(string $coin, string $interval): void
    {
        $redisKey = $this->getRedisKey($coin, $interval);

        try {
            // 先从Redis获取，命中则直接处理昨日数据
            $cachedData = Redis::get($redisKey);
            if ($cachedData !== false) {
                $data = unserialize($cachedData);
                $this->setYesterdayData($redisKey, $data);
                Log::info("K线数据命中缓存：{$redisKey}");
                return;
            }

            // 缓存未命中，从API拉取
            $data = $this->fetchKlineFromApi($coin, $interval);
            if (empty($data)) {
                Log::error("API获取K线数据为空：{$coin} {$interval}");
                return;
            }

            // 存储到Redis（设置10分钟过期，避免缓存膨胀）
            Redis::set($redisKey, serialize($data), ['NX', 'EX'=>1200]);
            $this->setYesterdayData($redisKey, $data);
            Log::info("K线数据更新成功：{$redisKey}");

        } catch (Exception $e) {
            Log::error("处理K线数据失败（{$coin} {$interval}）：{$e->getMessage()}");
        }
    }

    /**
     * 提取并存储昨日K线数据（修复原逻辑bug）
     */
    private function setYesterdayData(string $redisKey, array $data): void
    {
        // 只处理日线数据（1day）
        if (strpos($redisKey, '1day') === false) {
            return;
        }

        $currentDate = date("Y-m-d",strtotime("-1 day"));
        $coin = $this->extractCoinFromRedisKey($redisKey); // 从键名提取币种
        foreach ($data as $kline) {
            // 验证K线数据格式（必须包含时间戳id和币种code）
            if (!isset($kline['id']) || !is_numeric($kline['id'])) {
                Log::warning("无效K线数据格式：" . json_encode($kline));
                continue;
            }

            // 匹配当前日期的K线（当日数据）
            if (date("Y-m-d", (int)$kline['id']) === $currentDate) {
                $yesterdayKey = $this->getYesterdayRedisKey($coin);
                Redis::set($yesterdayKey, serialize($kline));
                Log::info("已更新昨日K线数据：{$yesterdayKey}");
                break; // 找到后退出循环，避免重复处理
            }
        }
    }

    /**
     * 从API拉取K线数据（使用cURL替代file_get_contents，增强可控性）
     */
    private function fetchKlineFromApi(string $coin, string $interval): mixed
    {
        $url = str_replace(
            ['[coin]', '[size]', '[period]'],
            [$coin, self::DATA_NUM, $interval],
            self::HUOBI_API_URL
        );

        try{
            $data = file_get_contents($url);
            if (empty($data)) {
                throw new \Exception("Failed to read: $url");
            }
            $data = json_decode($data,true);;
            if ($data===false) {
                return false;
            }
            if (empty($data["status"])) {
                return false;
            }
            if ($data["status"]=="ok") {
                $data["data"]["code"] = $coin;
                return $data['data'];
            }

        } catch (\Exception $e) {
            Log::error("http获取火币错误".$e->getMessage());
        }
    }


    /**
     * 生成Redis键名（封装避免重复拼接）
     */
    private function getRedisKey(string $coin, string $interval): string
    {
        return sprintf(self::REDIS_KEY_PREFIX, $coin) . $interval;
    }

    /**
     * 生成昨日K线的Redis键名
     */
    private function getYesterdayRedisKey(string $coin): string
    {
        return sprintf(self::REDIS_KEY_PREFIX . self::REDIS_YESTERDAY_SUFFIX, $coin);
    }

    /**
     * 从Redis键名中提取币种（辅助方法）
     */
    private function extractCoinFromRedisKey(string $redisKey): string
    {
        $prefix = 'market.';
        $suffix = 'usdt.kline.';
        $time ="1day";
        return str_replace([$prefix, $suffix,$time], '', $redisKey);
    }


}